package hust.hiepdv.javaproject.networking;

import hust.hiepdv.javaproject.constant.Constant;
import hust.hiepdv.javaproject.model.Ball;
import hust.hiepdv.javaproject.model.Pad;
import hust.hiepdv.javaproject.view.ServerView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

import javax.management.RuntimeErrorException;

public class TCPServer implements Runnable, Constant {

	private final int serverPort = SERVER_TCP_PORT;
	private ServerSocket severSocket = null;
	private boolean isStopped = false;
	private boolean isPlaying = false;
	private int clientCount = 0;
	private ClientThread[] clientThreads = new ClientThread[PLAYER_NUM];
	// private UDPServer udpServer;

	public Ball ball = new Ball();
	public Pad[] pads = new Pad[4];

	public TCPServer()
			throws Exception {
		this.ball = ServerView.ball;
		this.pads[0] = ServerView.pad1;
		this.pads[1] = ServerView.pad2;
		this.pads[2] = ServerView.pad3;
		this.pads[3] = ServerView.pad4;
	}

	@Override
	public void run() {
		try {
			severSocket = new ServerSocket(serverPort);
		} catch (IOException e) {
			throw new RuntimeException("Cannot open port " + serverPort, e);
		}

		while (!isStopped()) {
			Socket clientSocket = null;
			try {
				clientSocket = this.severSocket.accept();
				System.out.println(clientSocket.getInetAddress() + "//"
						+ clientSocket.getPort() + " connect");
				if (clientCount < PLAYER_NUM && !isPlaying) {
					if(clientCount == 1){
						clientThreads[2] = new ClientThread(clientSocket, 2);
						clientThreads[2].start();
						clientCount++;
					}
					else{
						for (int i = 0; i < PLAYER_NUM; i++) {
							if (clientThreads[i] == null) {
								// clientThreads[i] = new
								// ClientThread(clientSocket,
								// udpServer);
								clientThreads[i] = new ClientThread(
										clientSocket, i);
								clientThreads[i].start();
								clientCount++;
								break;
							}
						}
					}
				} else {
					PrintStream ps = new PrintStream(
							clientSocket.getOutputStream());
					ps.println(BUSY_MSG);
				}
			} catch (IOException e) {
				System.out.print(e);
			}
		}
	}

	public synchronized boolean isStopped() {
		return this.isStopped;
	}

	public synchronized boolean isPlaying() {
		return this.isPlaying;
	}

	public synchronized void setIsPlaying(boolean b) {
		isPlaying = b;
	}

	public synchronized void stop() {
		this.isStopped = true;
		this.setIsPlaying(false);
		for(int i =0; i<4; i++){
			if (clientThreads[i] != null) {
				clientThreads[i].outToClient.println(STOP_GAME);
				clientThreads[i].outToClient.close();
				try {
					clientThreads[i].inFromClient.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
			}
		}
		/*try {
			this.severSocket.close();
		} catch (IOException e) {
			throw new RuntimeException("Error closing server", e);
		}*/
		resetAll();
	}
	
	public void resetAll(){
		clientThreads = new ClientThread[PLAYER_NUM];
		isStopped = false;
		isPlaying = false;
		clientCount = 0;
	}

	public void startSend() {
		SendThread sendThread = new SendThread();
		Thread thread = new Thread(sendThread);
		String msg = "" + START_GAME;
		for(int i =0; i<4; i++){
			if (clientThreads[i] != null) {
				clientThreads[i].outToClient.println(msg);
			}
		}
		thread.start();
	}

	class SendThread extends Thread {
		@Override
		public void run() {
			// Thread start when the game started, it send update data to client
			// every 1000/24ms
			while (isPlaying()) {
				String s = packServerData();
				for (int i = 0; i < 4; i++) {
					if (clientThreads[i] != null) {
						clientThreads[i].outToClient.println(s);
					}
				}
				try {
					sleep(TIME_SLEEP);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public String packServerData() {
		StringBuilder sb = new StringBuilder();
		sb.append(DATA_MSG);
		sb.append("#");
		sb.append(ball.getX());
		sb.append("#");
		sb.append(ball.getY());
		for (int i = 0; i < 4; i++) {
			sb.append("#");
			sb.append(pads[i].getX());
			sb.append("#");
			sb.append(pads[i].getY());
		}
		sb.append("#");
		sb.append(ServerView.player1.getScore());
		sb.append("#");
		sb.append(ServerView.player2.getScore());
		sb.append("#");
		sb.append(ServerView.player3.getScore());
		sb.append("#");
		sb.append(ServerView.player4.getScore());
		return sb.toString();
	}

	class ClientThread extends Thread implements Constant {

		private Socket clientSocket = null;
		BufferedReader inFromClient;
		PrintStream outToClient;
		int id;

		// private UDPServer UDPServer = null;

		/*
		 * public ClientThread(Socket clientSocket, UDPServer UDPServer){
		 * this.clientSocket = clientSocket; this.UDPServer = UDPServer; }
		 */
		public ClientThread(Socket clientSocket, int i) {
			this.clientSocket = clientSocket;
			this.id = i;
		}

		public void run() {
			try {
				inFromClient = new BufferedReader(new InputStreamReader(
						clientSocket.getInputStream()));
				outToClient = new PrintStream(clientSocket.getOutputStream());
				while (true) {
					String line = inFromClient.readLine();
					/*System.out.println(clientSocket.getInetAddress() + "//"
							+ clientSocket.getPort() + " :" + line);*/
					String[] msgItems = line.split("#");
					if (msgItems[0].equals("" + CLIENT_STOP_PLAY_MSG)) {
						break;
					} else {
						handleMsg(msgItems);
					}
				}

				/*
				 * Close the output, input stream and socket
				 */
				inFromClient.close();
				outToClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		public void handleMsg(String[] msgItems) {
			int commandCode = Integer.parseInt(msgItems[0]);
			switch (commandCode) {
			case JOIN_GAME_MSG:
				String s = JOIN_SUCCESS_MSG + "#" + id;
				outToClient.println(s);
				break;
			case DATA_MSG:
				handleData(id, msgItems);
				break;
			default:
				break;
			}
		}
		
		public void handleData(int id, String[] msgItems){
			switch (id) {
			case 0:
				pads[0].setX(Integer.parseInt(msgItems[1]));
				break;
			case 1:
				pads[1].setY(Integer.parseInt(msgItems[1]));
				break;
			case 2:
				pads[2].setX(TABLE_SIZE - Integer.parseInt(msgItems[1]) - PAD_LENGTH);
				break;
			case 3:
				pads[3].setY(TABLE_SIZE - Integer.parseInt(msgItems[1]) - PAD_LENGTH);
				break;
			default:
				break;
			}
		}
	}

	public int getClientCount() {
		return clientCount;
	}

}
